﻿using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;

namespace Hook
{
    [ComVisibleAttribute(false), System.Security.SuppressUnmanagedCodeSecurity()]
    public class CHook : IDisposable
    {
        #region Variables

        private Hook.CWindowsAPI.EventProcessMode _EventProcessMode = Hook.CWindowsAPI.EventProcessMode.ApplicationProcess;
        private IntPtr _HookId = IntPtr.Zero;

        // Keep a reference so that the GC won't wipe it!
        private CWindowsAPI.InternalCallbackHandler _InternalCallback;

        /// <summary>
        /// Occurs when a certain system events fire.
        /// </summary>
        public event EventHandler<CallbackEventArgs> Callback;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the process mode in which the event should happen.
        /// </summary>
        /// <value>
        ///	The process mode in which the event should happen.
        ///	</value>
        public Hook.CWindowsAPI.EventProcessMode EventProcessMode
        {
            get { return _EventProcessMode; }
            set { _EventProcessMode = value; }
        }

        #endregion

        #region A

        /// <summary>
		/// Initializes a new instance of the WindowsEventsListener class.
		/// </summary>
		/// <param name="hookType">
		///		The type of hook procedure to be installed.
		///	</param>
        public CHook(Hook.CWindowsAPI.HookType hookType)
		{
			this._InternalCallback = new Hook.CWindowsAPI.InternalCallbackHandler(InternalCallback);

			this._HookId = CWindowsAPI.SetWindowsHookEx(hookType, _InternalCallback, CWindowsAPI.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);
            //this._HookId = CWindowsAPI.SetWindowsHookEx(hookType, _InternalCallback, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
		}

        /// <summary>
		/// Initializes a new instance of the WindowsEventsListener class.
		/// </summary>
		/// <param name="hookType">
		///		The type of hook procedure to be installed.
		///	</param>
		/// <param name="eventProcessMode">
		///		The process mode in which the event should happen.
		///	</param>
        public CHook(Hook.CWindowsAPI.HookType hookType, Hook.CWindowsAPI.EventProcessMode eventProcessMode) : this(hookType)
		{
			this._EventProcessMode = eventProcessMode;
		}

        public void Start(Hook.CWindowsAPI.HookType hookType)
        {
			this._HookId = CWindowsAPI.SetWindowsHookEx(hookType, _InternalCallback, CWindowsAPI.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);
        }

        #endregion

        #region Disposable


        /// <summary>
        /// Finalizes an instance of the WindowsEventsListener class.
        /// </summary>
        ~CHook()
        {
            CWindowsAPI.UnhookWindowsHookEx(_HookId);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            CWindowsAPI.UnhookWindowsHookEx(_HookId);
            GC.SuppressFinalize(this);
        }

        public void Stop()
        {
            CWindowsAPI.UnhookWindowsHookEx(_HookId);
        }

        #endregion

        #region

        private IntPtr InternalCallback(int code, IntPtr wordParam, IntPtr longParam)
        {
            bool _Handled = false;
            bool shouldCallbackFire = _EventProcessMode == CWindowsAPI.EventProcessMode.AnyProcess || CWindowsAPI.GetFocus() != IntPtr.Zero;

            if (code >= 0 && shouldCallbackFire)
            {
                CallbackEventArgs _EventArgs = new CallbackEventArgs(wordParam, longParam);
                OnCallback(_EventArgs);
                _Handled = _EventArgs.Handled;
            }

            return _Handled == false ? CWindowsAPI.CallNextHookEx(_HookId, code, wordParam, longParam) : new IntPtr(1);
        }

        /// <summary>
        /// Raises the Callback event.
        /// </summary>
        /// <param name="e">
        ///	The CallbackEventArgs instance containing the event data.
        ///	</param>
        private void OnCallback(CallbackEventArgs e)
        {
            EventHandler<CallbackEventArgs> _Handler = Callback;
            if (_Handler != null)
            {
                _Handler(this, e);
            }
        }

        #endregion
    }
}
